#pragma once
#ifndef MESH__EDITOR__PROJECT__HH__
#define MESH__EDITOR__PROJECT__HH__

#include <LitEditorProject.h>
#include <LitCore.h>
#include <LitSceneManager.h>
#include <LitEditorMeshSelecterDialog.h>
#include <LitEditorFileDialog.h>
#include <LitEditorMeshConverter.h>
#include "MeshFacingDir.h"

namespace Lit
{
	class BadMeshDefinition : public RuntimeError
	{
	public:
		BadMeshDefinition( const std::string &filename, const std::string &why, const std::string &mesh, const std::string &function, size_t line, const std::string &file )
			: RuntimeError( Lit::EXC_RUNTIME_ERROR, "MeshDefinition("+filename+") for mesh '"+mesh+"' is invalid because: "+why, function, line, file )
		{}
		
		BadMeshDefinition( const std::string &filename, const std::string &why, const std::string &function, size_t line, const std::string &file )
			: RuntimeError( Lit::EXC_RUNTIME_ERROR, "MeshDefinition("+filename+") is invalid because: "+why, function, line, file )
		{}

		~BadMeshDefinition( )
		{}
	};
	namespace Editor
	{
		class ObjMeshFormat
		{
		public:
			typedef Lit::Vec3 Vertex;
			typedef Lit::Vec3 Normal;
			typedef Lit::Vec2 TexCoord;
			struct VertexInfos
			{
				VertexInfos( ) : v(0), n(0), t(0){}
				VertexInfos( size_t _v, size_t _n=0, size_t _t = 0 )
					: v(_v), n(_n), t(_t){}
				VertexInfos( const VertexInfos& rk ) : v(rk.v), n(rk.n), t(rk.t){}

				size_t v;
				size_t n;
				size_t t;
			};
			struct FaceInfos
			{
				FaceInfos( ){}
				FaceInfos( const VertexInfos& vv0, const VertexInfos& vv1, const VertexInfos& vv2 ) : v0(vv0), v1(vv1), v2(vv2), is_quad( false ){}
				FaceInfos( const VertexInfos& vv0, const VertexInfos& vv1, const VertexInfos& vv2, const VertexInfos& vv3 ) : v0(vv0), v1(vv1), v2(vv2), v3(vv3), is_quad( true ){}
				FaceInfos( const FaceInfos& rk ) : v0( rk.v0 ), v1( rk.v1 ), v2(rk.v2), v3(rk.v3), is_quad(rk.is_quad){}
				
				bool is_quad;
				VertexInfos v0;
				VertexInfos v1;
				VertexInfos v2;
				VertexInfos v3;
			};

			typedef std::vector<Vertex> VertexList;
			typedef std::vector<Normal> NormalList;
			typedef std::vector<TexCoord> TexCoordList;
			struct FaceList
			{
				FaceList() : numTris(0), numQuads(0), material("BaseWhite"){}
				~FaceList(){}

				std::vector<FaceInfos> list;
				size_t numTris, numQuads;
				std::string material;
			};
			typedef std::map<std::string,FaceList> FaceMap;

			static void processComment( const std::string &str, size_t line_id );
			static void processVertex( const std::string &str, size_t line_id, VertexList & list );
			static void processNormal( const std::string &str, size_t line_id, NormalList & list );
			static void processTextureCoord( const std::string &str, size_t line_id, TexCoordList & list );
			static void processFace( const std::string &str, size_t line_id, FaceMap & list, const std::string &grp );
			static ObjMeshFormat::VertexInfos processFaceInfos( const std::string &str, size_t line_id );
			static void processObject( const std::string &str, size_t line_id, std::string & grp );
			static void processGroup( const std::string &str, size_t line_id, std::string & group );
			static void processUseMtl( const std::string &str, size_t line_id, std::string & material );
			static void processMaterialLib( const std::string &str, size_t line_id, FaceList& lst );
			static void processMaterialLibLine( const std::string &word, const std::string &rest, size_t line_id, Ogre::MaterialPtr mat );
		};
		class MeshConverterObjFormat : public MeshConverter::Format
		{

		public:
			MeshConverterObjFormat( );
			~MeshConverterObjFormat();

			Ogre::MeshPtr createMesh( const std::string &ogre_mesh_name );
			Ogre::MeshPtr importMesh( const std::string &input_filename, const std::string &ogre_mesh_name );
			void exportMesh( const std::string &input_filename, Ogre::MeshPtr mesh );
		
			Lit::Vec3 findNormal( ObjMeshFormat::FaceList * list, const Lit::Vec3 &vert )
			{
				if( !list ) return Lit::Vec3::ZERO;
				for( auto i=list->list.begin(); i!=list->list.end(); i++ )
				{
					if( i->v0.v < mVertices.size() && mVertices[i->v0.v] == vert && i->v0.n < mNormals.size() ) return mNormals[i->v0.n];
					if( i->v1.v < mVertices.size() && mVertices[i->v1.v] == vert && i->v1.n < mNormals.size() ) return mNormals[i->v1.n];
					if( i->v2.v < mVertices.size() && mVertices[i->v2.v] == vert && i->v2.n < mNormals.size() ) return mNormals[i->v2.n];
					if( i->is_quad && 
						i->v3.v < mVertices.size() && 
						mVertices[i->v3.v] == vert && 
						i->v3.n < mNormals.size() ) 
						return mNormals[i->v3.n];
				}
				return Lit::Vec3::ZERO;
			}

			ObjMeshFormat::VertexList		mVertices;
			ObjMeshFormat::NormalList		mNormals;
			ObjMeshFormat::TexCoordList		mTexCoords;
			ObjMeshFormat::FaceMap			mFaces;
			std::string						mGroup;
		};
		class MeshConverterOGREFormat : public MeshConverter::Format
		{
		public:
			MeshConverterOGREFormat( );
			~MeshConverterOGREFormat( );

			Ogre::MeshPtr importMesh( const std::string &input_filename, const std::string &ogre_mesh_name );
			void exportMesh( const std::string &input_filename, Ogre::MeshPtr mesh );
		};
		class MeshProject : public Project
		{
			Q_OBJECT

		public:
			MeshProject( Instance * inst, QTreeWidget*w, const std::string &name = "unnamed" );
			~MeshProject( );

			MeshFacingDir*	getFacingDir(){ return mFacingDir; }
			const MeshFacingDir* getFacingDir() const{ return mFacingDir; }
			
			const Lit::Entity * getEntity() const;
			Lit::Entity* getEntity();

			void fitEntity( );
			void create( const std::string &mesh, bool dynamic = true, Lit::Physics::ShapeType shape = Lit::Physics::ST_BOX, Lit::Real mass = 1.0f, const Lit::Vec3 &offset = Lit::Vec3::ZERO );
			void create( );

			void open( const std::string &filename = "" );
			void save( const std::string &filename = "" );

			void importMesh( const std::string &filename = "" );
			void exportMesh( const std::string &filename = "" );

			void createPack();

			const Physics::BodyPtr& getBody() const;
			static FilePath BasePath;
			static FilePath ImportPath;
			static FilePath ExportPath;
			static FilePath UserProjectPath;

		protected:
			MeshConverter			mMeshConverter;
			Lit::EntityPtr			mEntity;
			Lit::Physics::BodyPtr	mBody;
			Lit::Editor::Instance*	mInstance;
			MeshFacingDir*			mFacingDir;

			QCheckBox *				mPhysicsDynamic;
			QComboBox *				mPhysicsShape;
			QDoubleSpinBox*			mPhysicsMass;
			QLineEdit*				mPhysicsOffset;
			
			QComboBox *				mMaterialName;
			QComboBox *				mGeometryFacing;

			void _destroyProject( );

			void setupGeometryNode( QTreeWidgetItem * node );
			void setupPhysicsNode( QTreeWidgetItem * node );
			void setupMaterialNode( QTreeWidgetItem * node );
			void setupNodes( );

		protected slots:
			void materialChanged( const QString &to );
			void physicsShapeChanged( const QString &to );
			void geometryFacingDirChanged( const QString &to );
			void physicsDynamicStateChanged( bool state );
			void physicsMassChanged( double mass );
		};
		class MeshPackExporterConfig : public QDialog
		{
			Q_OBJECT
			QGridLayout *	mLayout;
			QFormLayout *	mGroupLayout;
			QGroupBox *		mGroup;
			QCheckBox*		mMaterialExport;
			QCheckBox*		mTextureExport;
			QCheckBox*		mMeshExport;
			QCheckBox*		mMeshDefExport;
			QTableWidget*	mResources;

			MeshProject *	mProject;
		public:
			MeshPackExporterConfig( QWidget * parent ) : QDialog( parent )
			{
				mProject = NULL;
				mLayout = new QGridLayout(this);
				mGroup = new QGroupBox("Export Mesh Pack", this );
				mResources = new QTableWidget( mGroup );
				mGroupLayout = new QFormLayout(mGroup);
				mMeshExport = new QCheckBox(mGroup);
				mTextureExport = new QCheckBox(mGroup);
				mMaterialExport = new QCheckBox(mGroup);
				mMeshDefExport = new QCheckBox(mGroup);

				mMeshDefExport->setChecked( true );
				mTextureExport->setChecked( true );
				mMeshExport->setChecked( true );
				mMaterialExport->setChecked( true );

				QDialogButtonBox * box = new QDialogButtonBox( Qt::Horizontal, this );
				box->addButton( QDialogButtonBox::Ok );
				box->addButton( QDialogButtonBox::Cancel );

				mGroupLayout->addRow( "Export Mesh", mMeshExport );
				mGroupLayout->addRow( "Export Mesh Definition", mMeshDefExport );
				mGroupLayout->addRow( "Export Material", mMaterialExport );
				mGroupLayout->addRow( "Export Textures", mTextureExport );
				mGroup->setLayout( mGroupLayout );
				mLayout->addWidget( mGroup );
				
				mLayout->addWidget( mResources );
				mLayout->addWidget( box );
				connect( box, SIGNAL(accepted()), (QDialog*)this, SLOT(accept()) );
				connect( box, SIGNAL(rejected()), (QDialog*)this, SLOT(reject()) );
				
				connect( mMeshExport, SIGNAL(toggled(bool)), this, SLOT(updateResourcesList()) );
				connect( mMaterialExport, SIGNAL(toggled(bool)), this, SLOT(updateResourcesList()) );
				connect( mTextureExport, SIGNAL(toggled(bool)), this, SLOT(updateResourcesList()) );
				connect( mMeshDefExport, SIGNAL(toggled(bool)), this, SLOT(updateResourcesList()) );
				mResources->clearContents();
				mResources->setColumnCount( 2 );
				mResources->setHorizontalHeaderLabels( QStringList("Resource Name") << "Export Filename" );
				setLayout( mLayout );
			}
			~MeshPackExporterConfig(){}

			void setupUi( MeshProject * proj  )
			{
				mProject = proj;
			}

			int exec( MeshProject * proj )
			{
				setupUi( proj ); 
				updateResourcesList();
				std::string out = FileDialog::getSaveFileName( proj->getInstance(), "Export Mesh Pack", MeshProject::ExportPath, "Mesh Pack Files ( *.zmesh );;" ).str();
				if( out.empty() ) return QDialog::Rejected;
				int ret = QDialog::exec( );
				if( ret == QDialog::Accepted )
				{
					std::string projName = Ogre::StringUtil::replaceAll( mProject->getName(), ".mesh", "" );
					std::string dir = MeshProject::ExportPath.str() +"/"+projName+"/";
					Lit::FileSystem::createDirectories( dir );
					std::vector<std::string> files;
					Ogre::MeshSerializer().exportMesh( mProject->getEntity()->getMesh().get(), dir+projName+".mesh" );
					Ogre::MaterialPtr mat(Ogre::MaterialManager::getSingleton().getByName(mProject->getEntity()->getMaterialName()));
					Ogre::MaterialSerializer().exportMaterial( mat, dir+projName+".material" );
					files.push_back( dir+projName+".mesh" );
					files.push_back( dir+projName+".material" );
					for( unsigned short t=0; t<mat->getNumTechniques(); t++ )
					{
						Ogre::Technique * tech = mat->getTechnique( t );
						for( unsigned short p=0; p<tech->getNumPasses(); p++ )
						{
							Ogre::Pass * pass = tech->getPass( p );
							for( unsigned short tu=0; tu<pass->getNumTextureUnitStates(); tu++ )
							{
								Ogre::TextureUnitState * ts = pass->getTextureUnitState(tu);
								Ogre::FileInfo p(findResourceLocation( ts->getTextureName() ));
								if( !p.filename.empty() && p.archive && p.archive->getType() == "FileSystem" )
								{
									std::string fpath(p.archive->getName()+"/"+p.filename);
									boost::filesystem::remove( dir+p.basename );
									boost::filesystem::copy_file( fpath, dir+p.basename );
									files.push_back( dir+p.basename );
								}
							}
						}
					}
					mProject->save( dir+projName+".meshdef" );
					files.push_back(dir+projName+".meshdef");
					for( auto i=files.begin(); i!=files.end(); i++ )
						LIT_LOG_DBG("Pack File: "+(*i));
					QProcess proc( mProject->getInstance() );
					proc.start( TO_QT("editor/7za.exe a \""+out+"\" \""+dir+"\\*\"") );
					Lit::FileSystem::removeAll(dir);
				}
				return ret;
			}
			Ogre::FileInfo findResourceLocation( const std::string &res_name )
			{	
				std::string resourceGroup;
				try{
					resourceGroup=Ogre::ResourceGroupManager::getSingletonPtr()->findGroupContainingResource(res_name);
				}catch(...){ return Ogre::FileInfo(); }
				Ogre::FileInfoListPtr fileInfoList(Ogre::ResourceGroupManager::getSingletonPtr()->findResourceFileInfo(resourceGroup, res_name));
				if( fileInfoList->empty() )
					return Ogre::FileInfo();
				return fileInfoList->at(0);
			}
			bool nothingToExport() const{ return !shouldExportMesh() && !shouldExportMeshDef() && !shouldExportMaterial() && !shouldExportTextures(); }
			bool shouldExportMesh() const{ return mMeshExport->isChecked(); }
			bool shouldExportMeshDef() const{ return mMeshDefExport->isChecked(); }
			bool shouldExportMaterial() const{ return mMaterialExport->isChecked(); }
			bool shouldExportTextures() const{ return mTextureExport->isChecked(); }

			void addTableItem( const std::string &res_name, const std::string &export_name )
			{
				size_t id = mResources->rowCount();
				mResources->setRowCount( id+1 );
				mResources->setItem( id, 0, new QTableWidgetItem(TO_QT(res_name)) );
				mResources->setItem( id, 1, new QTableWidgetItem(TO_QT(export_name)) );
			}
		protected slots:
			void updateResourcesList( )
			{
				if( !mProject ) return;
				mResources->clearContents();
				mResources->setRowCount( 0 );
				std::string name = Ogre::StringUtil::replaceAll( mProject->getName(), ".mesh", "" );
				if( mMeshExport->isChecked() ) addTableItem(mProject->getEntity()->getMeshName(),mProject->getEntity()->getMeshName());
				if( mMeshDefExport->isChecked() ){
					addTableItem( mProject->getSavePath().empty()?"":mProject->getSavePath(), name+".meshdef" );
				}
				if( mTextureExport->isChecked() ){
					Ogre::MaterialPtr mat(Ogre::MaterialManager::getSingleton().getByName(mProject->getEntity()->getMaterialName()));
					for( unsigned short iT = 0; iT<mat->getNumTechniques(); iT++ )
					{
						Ogre::Technique * tech = mat->getTechnique(iT);
						for( unsigned short iP = 0; iP<tech->getNumPasses(); iP++ )
						{
							Ogre::Pass * pass = tech->getPass( iP );
							for( unsigned short iU = 0; iU<mat->getTechnique(0)->getPass(0)->getNumTextureUnitStates(); iU++ )
							{
								Ogre::TextureUnitState * unit = pass->getTextureUnitState(iU);
								addTableItem( unit->getTextureName(), unit->getTextureName() );
							}
						}
					}
				}
				if( mMaterialExport->isChecked() ){
					addTableItem( mProject->getEntity()->getMaterialName(), name+".material" );
				}
			}
		};
	};
};

#endif